/*************************************************************
* ds_param_store.cpp
*
* Configuration file functions
*************************************************************/
#include <ap/settings/ds_settings.h>

#include "ap\ap_comm.h"
#include "m3d.h"
#include "ds_string.h"
#include "ds_vector.h"
#include "ds_map.h"
#include "ds_smart_ptr.h"
#include "fileio.h"
#include "ps_decl.h"
#include "ds_param.h"
#include "ds_param_store.h"

//////////////////////////////////////////////////////////////////////////
//
// class dsPARAM_STORE
//
//////////////////////////////////////////////////////////////////////////
#define PARAM_STREAM_NOT_IN_DICT -1
#define PARAM_STREAM_END         -2

#define PARAM_VERSION_DEF_INI    0
#define PARAM_VERSION_DEF_STREAM -1
static const char* versionTag = "version";

/*************************************************************
* dsPARAM_STORE::dsPARAM_STORE ()
*************************************************************/
dsPARAM_STORE::dsPARAM_STORE() : items(AP_CL), indexes(AP_CL), dictionary(AP_CL), dictionary_indexes(AP_CL)
#ifndef _RETAIL
   , dictionary_new(AP_CL)
#endif
{
   itemsRead = 0;
   return;
}

/*************************************************************
* dsPARAM_STORE::dsPARAM_STORE ()
*************************************************************/
dsPARAM_STORE::dsPARAM_STORE(dsSTRING path, BOOL loadDictionary, STRING_LOADER *pLoader) : items(AP_CL), indexes(AP_CL), dictionary(AP_CL), dictionary_indexes(AP_CL)
#ifndef _RETAIL
, dictionary_new(AP_CL)
#endif
{
   itemsRead = 0;
   ReadIni(path, loadDictionary, pLoader);
   return;
}

/*************************************************************
* dsPARAM_STORE::FromString ()
*************************************************************/
template <> bool dsPARAM_STORE::FromString<bool>(dsSTRING src)
{
   src.ToLower();
   return src == "yes" || src == "true";
}

/*************************************************************
* dsPARAM_STORE::FromString ()
*************************************************************/
template <> int dsPARAM_STORE::FromString<int>(dsSTRING src)
{
   return atoi(src.CStr());
}

/*************************************************************
* dsPARAM_STORE::FromString ()
*************************************************************/
template <> DWORD dsPARAM_STORE::FromString<DWORD>(dsSTRING src)
{
   int radix = 10;
   src.ToLower();
   if (src.Find("0x") == 0) {
      src.Erase(0, 2);
      radix = 16;
   }
   return strtoul(src.CStr(), NULL, radix);
}

/*************************************************************
* dsPARAM_STORE::FromString ()
*************************************************************/
template <> float dsPARAM_STORE::FromString<float>(dsSTRING src)
{
   return static_cast<float>(atof(src.CStr()));
}

/*************************************************************
* dsPARAM_STORE::FromString ()
*************************************************************/
template <> dsSTRING dsPARAM_STORE::FromString<dsSTRING>(dsSTRING src)
{
   src.Erase(0, 1);
   src.Erase(src.Length() - 1, 1);
   return src;
}

/*************************************************************
* dsPARAM_STORE::ToString ()
*************************************************************/
template <> dsSTRING dsPARAM_STORE::ToString<bool>(bool src)
{
   return src ? "yes" : "no";
}

/*************************************************************
* dsPARAM_STORE::ToString ()
*************************************************************/
template <> dsSTRING dsPARAM_STORE::ToString<int>(int src)
{
   dsSTRING out = dsSPrintf("%d", src);
   return out;
}

/*************************************************************
* dsPARAM_STORE::ToString ()
*************************************************************/
template <> dsSTRING dsPARAM_STORE::ToString<DWORD>(DWORD src)
{
   dsSTRING out = "0x" + dsSPrintf("%x", src);
   return out;
}

/*************************************************************
* dsPARAM_STORE::ToString ()
*************************************************************/
template <> dsSTRING dsPARAM_STORE::ToString<float>(float src)
{
   dsSTRING out = dsSPrintf("%f", src);
   return out;
}

/*************************************************************
* dsPARAM_STORE::ToString ()
*************************************************************/
template <> dsSTRING dsPARAM_STORE::ToString<dsSTRING>(dsSTRING src)
{
   dsSTRING out = "\"";
   out += src;
   out += "\"";
   return out;
}

/*************************************************************
* dsPARAM_STORE::ReadDictionary ()
*************************************************************/
void dsPARAM_STORE::ReadDictionary(dsSTRING path)
{
   osFILE_HANDLE fp;
   char          *str;
   int           fileSize;

   if ((fp = osFileOpen(path.CStr(), OS_FILE_READ)) == NULL) {
      apLog("~Error~Cannot open dictionary file %s", path.CStr());
      return;
   }

   fileSize = osFileGetSize(fp);

   if ((str = (char*)malloc(fileSize + 1)) == NULL) {
      return;
   }

   if (osFileRead(fp, str, fileSize) != fileSize) {
      return;
   }
   osFileClose(fp);

   str[fileSize] = 0;

   Parse(str, TRUE);

   free(str);
   return;
}

/*************************************************************
* dsPARAM_STORE::ReadDictionary ()
*************************************************************/
#ifndef _RETAIL
void dsPARAM_STORE::WriteDictionary(dsSTRING path)
{
   if (dictionary_new.Length() == 0) {
      return;
   }

   osFILE_HANDLE fp;
   // set normal file attriutes in case config's RO
   if (osFileIsReadOnly(path.CStr())) {
      osFileSetNormal(path.CStr());
   }

   if ((fp = osFileOpen(path.CStr(), OS_FILE_WRITE | OS_FILE_CREATE)) == NULL) {
      apLog("~Error~Cannot open CFG file %s", path.CStr());
      ASSERT(FALSE);
      return;
   }

   char buffer[AP_BUFSIZE];
   for (int i = 0; i < dictionary_new.Length(); i++) {
      sprintf(buffer, "%s\r\n", dictionary_new[i].CStr());
      osFileWrite(fp, buffer, strlen(buffer));
   }
   sprintf(buffer, "\r\n");
   osFileWrite(fp, buffer, strlen(buffer));

   osFileClose(fp);
   return;
}
#endif //_RETAIL

/*************************************************************
* dsPARAM_STORE::ReadIni ()
*************************************************************/
BOOL dsPARAM_STORE::ReadIni(dsSTRING path, BOOL loadDictionary, STRING_LOADER *pLoader)
{
   if (pLoader) {
      dsSTRING userIniStr = (*pLoader)(path.CStr());

      if (!userIniStr.IsEmpty()) {
         if (!Parse(userIniStr.CHStr())) {
            return FALSE;
         }

         itemsRead = items.Length();

         if (loadDictionary) {
            dsSTRING pathToDict = path;
            pathToDict += ".dict";

            dsSTRING userIniDictStr = (*pLoader)(pathToDict.CStr());
            if (!userIniDictStr.IsEmpty()) {
               Parse(userIniDictStr.CHStr(), TRUE);
            }
         }

         return TRUE;
      }
   }


   osFILE_HANDLE fp;
   char          *str;
   int           fileSize;

   if ((fp = osFileOpen(path.CStr(), OS_FILE_READ)) == NULL) {
      apLog("~Error~Cannot open/create CFG file %s", path.CStr());
      return FALSE;
   }

   fileSize = osFileGetSize(fp);

   if ((str = (char*)malloc(fileSize + 1)) == NULL) {
      return FALSE;
   }

   if (osFileRead(fp, str, fileSize) != fileSize) {
      return FALSE;
   }
   osFileClose(fp);

   str[fileSize] = 0;
   if (!Parse(str)) {
      free(str);
      return FALSE;
   }
   free(str);

   itemsRead = items.Length();

   if (loadDictionary) {
      dsSTRING pathToDict = path;
      pathToDict += ".dict";
      ReadDictionary(pathToDict);
   }

   return TRUE;
}

/*******************************************************
* dsPARAM_STORE::Parse ()
*******************************************************/
BOOL dsPARAM_STORE::Parse (char *string, BOOL isDictionary)
{
   char        *str, *p, *strNew;
   char         bufStr[AP_BUFSIZE];
   int          line; 

   line = 0;
   str  = string;
   strNew = str;

/*
   // get the first line
   str = ParseNextString(str, bufStr);
   while (str != NULL && strlen(str) == 0) {
      str = ParseNextString(str, bufStr);
      line ++;
   }
   if (str == NULL) {
      // empty string
      apLogErr("CFG file is empty !");
      return TRUE;
   }
*/

   // add variables to this section
   while (TRUE) {
      str = strNew;
      if (str == NULL) {
         // end of main string
         return TRUE;
      }

      strNew = ParseNextString(str, bufStr);
      line ++;
      // process this line
      if (strlen(bufStr) == 0) {
         // empty line
         continue;
      }

      if (!isDictionary) {
         p = bufStr;

         // skip spaces
         while (*p && (*p == ' ' || *p == '\t')) {
            p++;
         }

         if (p[0] == ';' || p[0] == '[' || (p[0] == '/' && p[1] == '/')) {
            // that's a comment string
            continue;
         }

         // bufStr is now the new line
         p = strchr(bufStr, '=');
         if (p == NULL) {
            apLog("~Error~CFG file line %d: No equal sign !", line);
            continue;
         }
         *p = 0;
         ParseStripSpaces(bufStr);

         psSTRID keyName(bufStr, FALSE);
         ParseStripSpaces(++p);
         dsSTRING keyVal = p;

         AddKeyVal(keyName, keyVal);
      } else {
         ParseStripSpaces(bufStr);
         if (bufStr[0] == ';' || (bufStr[0] == '/' && bufStr[1] == '/')) {
            // that's a comment string
            continue;
         }
         psSTRID keyName(bufStr, FALSE);
         dictionary.PushBack(keyName);
         dictionary_indexes.Insert(keyName, dictionary.Length() - 1);
      }
   }
   return TRUE;
}

/*******************************************************
* dsPARAM_STORE::ParseNextString()
*******************************************************/
char* dsPARAM_STORE::ParseNextString (char *stringMain, char *bufStr)
{
   char *p;

   p = strchr(stringMain, '\r');
   if (p == NULL) {
      strcpy(bufStr, stringMain);
      return NULL;
   }
   ASSERT(*(p+1) == '\n');

   _StrNCopy(bufStr, stringMain, p - stringMain + 1);
   ParseStripSpaces(bufStr);
   return (p+2);
}

/*******************************************************
* dsPARAM_STORE::ParseStripSpaces()
*******************************************************/
void dsPARAM_STORE::ParseStripSpaces(char *string)
{
   char *p1, *p2;
   int   len, i;

   // skip beginning spaces / tabs
   p1 = string;
   while (*p1 && (*p1 == ' ' || *p1 == '\t')) {
      p1 ++;
   }

   // skip trailing spaces / tabs
   len = strlen(string);
   if (!len) {
      return;
   }

   p2 = string + len - 1;
   while (*p2 == ' ' || *p2 == '\t' && p2 >= string) {
      p2 --;
   }
//    if (p1 < p2 && *p1 == '"' && *p2 == '"') {
//       p1 ++;
//       p2 --;
//    }
   len = p2 - p1 + 1;
   for (i = 0; i < len; i++) {
      string[i] = p1[i];
   }
   string[i] = 0;

   return;
}

/*************************************************************
* dsPARAM_STORE::AddKeyVal ()
*************************************************************/
void dsPARAM_STORE::AddKeyVal(psSTRID key, dsSTRING val)
{
   dsPARAM temp;
   dsPARAM *pTemp;
   int itemIdx = Find(key);
   if (itemIdx == -1) {
      pTemp = &temp;
   } else {
      pTemp = &items[itemIdx];
   }

   switch(GetType(val)) {
      case TYPE_ID(bool):
         pTemp->SetVal<bool>(key, FromString<bool>(val));
         break;
      case TYPE_ID(int):
         pTemp->SetVal<int>(key, FromString<int>(val));
         break;
      case TYPE_ID(DWORD):
         pTemp->SetVal<DWORD>(key, FromString<DWORD>(val));
         break;
      case TYPE_ID(float):
         pTemp->SetVal<float>(key, FromString<float>(val));
         break;
      case TYPE_ID(dsSTRING):
         pTemp->SetVal<dsSTRING>(key, FromString<dsSTRING>(val));
         break;
   }
   if (itemIdx == -1) {
      items.PushBack(temp);
      indexes.Insert(key, items.Length() - 1);
   }
   return;
}

/*************************************************************
* dsPARAM_STORE::GetType ()
*************************************************************/
int dsPARAM_STORE::GetType(dsSTRING val)
{
   if (val[0] == '"') {
	   return TYPE_ID(dsSTRING);
   }
   val.ToLower();
   if (val == "yes"  || val == "no" ||
       val == "true" || val == "false") {
      return TYPE_ID(bool);
   }
   if (val[0] == '0' && val[1] == 'x') {
      return TYPE_ID(DWORD);
   }
   if (val.Find('.', 0, TRUE) != -1 || val.Find(',', 0, TRUE) != -1) {
      return TYPE_ID(float);
   }

   return TYPE_ID(int);
}

/*************************************************************
* dsPARAM_STORE::WriteIni ()
*************************************************************/
BOOL dsPARAM_STORE::WriteIni(dsSTRING path)
{
   osFILE_HANDLE fp;
   // set normal file attriutes in case config's RO
   if (osFileIsReadOnly(path.CStr())) {
      osFileSetNormal(path.CStr());
   }

   if ((fp = osFileOpen(path.CStr(), OS_FILE_WRITE | OS_FILE_CREATE)) == NULL) {
      apLog("~Error~Cannot open CFG file %s", path.CStr());
      ASSERT(FALSE);
      return FALSE;
   }

   char buffer[AP_BUFSIZE];
   for (int i = 0; i < items.Length(); i++) {
      dsSTRING strOut;
      psSTRID  id;

      switch(items[i].data.GetTypeId()) {
      case TYPE_ID(bool):
         {
            bool bTemp;
            id = items[i].GetVal<bool>(bTemp);
            strOut = ToString(bTemp);
         }
         break;
      case TYPE_ID(int):
         {
            int iTemp;
            id = items[i].GetVal<int>(iTemp);
            strOut = ToString(iTemp);
         }
         break;
      case TYPE_ID(DWORD):
         {
            DWORD dTemp;
            id = items[i].GetVal<DWORD>(dTemp);
            strOut = ToString(dTemp);
         }
         break;
      case TYPE_ID(float):
         {
            float fTemp;
            id = items[i].GetVal<float>(fTemp);
            strOut = ToString(fTemp);
         }
         break;
      case TYPE_ID(dsSTRING):
         {
            dsSTRING sTemp;
            id = items[i].GetVal<dsSTRING>(sTemp);
            strOut = ToString(sTemp);
         }
         break;
      }
      sprintf(buffer, "%s = %s\r\n", id.CStr(), strOut.CStr());
      osFileWrite(fp, buffer, strlen(buffer));
   }
   sprintf(buffer, "\r\n");
   osFileWrite(fp, buffer, strlen(buffer));

   osFileClose(fp);

/*
   if (dictionary.Length()) {
      dsSTRING pathToDict = path;
      pathToDict += ".dict";
      WriteDictionary(pathToDict);
   }
*/

   return TRUE;
}

/*************************************************************
* dsPARAM_STORE::ReadStream ()
*************************************************************/
BOOL dsPARAM_STORE::ReadStream(fioFILE_MEM *stream, BOOL forceDict)
{
   if (stream->GetStreamSize() == 0) {
      return FALSE;
   }

   BOOL retVal = FALSE;
   if (dictionary.Length() || forceDict) {
      retVal = ReadStreamDict(stream);
   } else {
      retVal = ReadStreamSimple(stream);
   }

   return retVal;
}

/*************************************************************
* dsPARAM_STORE::WriteStream ()
*************************************************************/
BOOL dsPARAM_STORE::WriteStream(fioFILE_MEM *stream, BOOL forceDict)
{
   BOOL retVal = FALSE;
   if (dictionary.Length() || forceDict) {
      retVal = WriteStreamDict(stream);
   } else {
      retVal = WriteStreamSimple(stream);
   }

   return retVal;
}

/*************************************************************
* dsPARAM_STORE::ReadStreamSimple()
*************************************************************/
BOOL dsPARAM_STORE::ReadStreamSimple(fioFILE_MEM *stream)
{
   for (int i = 0; i < itemsRead; ++i) {
      switch(items[i].data.GetTypeId()) {
      case TYPE_ID(bool):
         {
            BOOL bTemp;
            stream->Read(&bTemp);
            items[i].SetVal<bool>(items[i].GetId(), bTemp ? true : false);
         }
         break;
      case TYPE_ID(int):
         {
            int iTemp;
            stream->Read(&iTemp);
            items[i].SetVal<int>(items[i].GetId(), iTemp);
         }
         break;
      case TYPE_ID(DWORD):
         {
            DWORD dTemp;
            stream->Read(&dTemp);
            items[i].SetVal<DWORD>(items[i].GetId(), dTemp);
         }
         break;
      case TYPE_ID(float):
         {
            float fTemp;
            stream->Read(&fTemp);
            items[i].SetVal<float>(items[i].GetId(), fTemp);
         }
         break;
      case TYPE_ID(dsSTRING):
         {
            dsSTRING sTemp;
            stream->Read(sTemp);
            items[i].SetVal<dsSTRING>(items[i].GetId(), sTemp);
         }
         break;
      }
   }
   return TRUE;
}

/*************************************************************
* dsPARAM_STORE::WriteStreamSimple()
*************************************************************/
BOOL dsPARAM_STORE::WriteStreamSimple(fioFILE_MEM *stream)
{
   for (int i = 0; i < itemsRead; ++i) {
      switch(items[i].data.GetTypeId()) {
      case TYPE_ID(bool):
         {
            BOOL bTemp;
            bool bSrc;
            items[i].GetVal<bool>(bSrc);
            bTemp = bSrc;
            stream->Write(bTemp);
         }
         break;
      case TYPE_ID(int):
         {
            int iTemp;
            items[i].GetVal<int>(iTemp);
            stream->Write(iTemp);
         }
         break;
      case TYPE_ID(DWORD):
         {
            DWORD dTemp;
            items[i].GetVal<DWORD>(dTemp);
            stream->Write(dTemp);
         }
         break;
      case TYPE_ID(float):
         {
            float fTemp;
            items[i].GetVal<float>(fTemp);
            stream->Write(fTemp);
         }
         break;
      case TYPE_ID(dsSTRING):
         {
            dsSTRING sTemp;
            items[i].GetVal<dsSTRING>(sTemp);
            stream->Write(sTemp);
         }
         break;
      }
   }
   return TRUE;
}

/*************************************************************
* dsPARAM_STORE::ReadStreamDict()
*************************************************************/
BOOL dsPARAM_STORE::ReadStreamDict(fioFILE_MEM *stream)
{
   int      id;
   dsSTRING strId;
   psSTRID  psId;
   int      typeId = -1;
   int      itemIdx;
   int      version_1 = PARAM_VERSION_DEF_STREAM, version_2 = PARAM_VERSION_DEF_INI;

   // Check version
   stream->ReadPacked(&version_1);
   int idxVersion = Find(psSTRID(versionTag, FALSE));
   if (idxVersion != -1) {
      GetVal(idxVersion, version_2);
   }
   
   if (version_1 != version_2) {
      //ASSERT(FALSE);
      return FALSE;
   }

   while (TRUE) {
      stream->ReadPacked(&id);
      if (id == PARAM_STREAM_END) {
         break;
      }
      if (id == PARAM_STREAM_NOT_IN_DICT) {
         stream->Read(strId);
         stream->ReadPacked(&typeId);      
         psId = psSTRID(strId, FALSE);
      } else {
         ASSERT(id >= 0 && id < dictionary.Length());
         psId = dictionary[id];
      }
      itemIdx = Find(psId);

      if (itemIdx != -1) {
         typeId = items[itemIdx].data.GetTypeId();
      }

      switch(typeId) {
      case TYPE_ID(bool):
         {
            BOOL bTemp;
            stream->ReadPacked(&bTemp);
            if (itemIdx != -1) {
               items[itemIdx].SetVal<bool>(psId, bTemp ? true : false);
            } else {
               AddKey(psId, bTemp ? true : false);
            }
         }
         break;
      case TYPE_ID(int):
         {
            int iTemp;
            stream->ReadPacked(&iTemp);
            if (itemIdx != -1) {
               items[itemIdx].SetVal<int>(psId, iTemp);
            } else {
               AddKey(psId, iTemp);
            }
         }
         break;
      case TYPE_ID(DWORD):
         {
            DWORD dTemp;
            stream->ReadPacked(&dTemp);
            if (itemIdx != -1) {
               items[itemIdx].SetVal<DWORD>(psId, dTemp);
            } else {
               AddKey(psId, dTemp);
            }
         }
         break;
      case TYPE_ID(float):
         {
            float fTemp;
            stream->Read(&fTemp);
            if (itemIdx != -1) {
               items[itemIdx].SetVal<float>(psId, fTemp);
            } else {
               AddKey(psId, fTemp);
            }
         }
         break;
      case TYPE_ID(dsSTRING):
         {
            dsSTRING sTemp;
            stream->Read(sTemp);
            if (itemIdx != -1) {
               items[itemIdx].SetVal<dsSTRING>(psId, sTemp);
            } else {
               AddKey(psId, sTemp);
            }
         }
         break;
      default:
         ASSERT_ALOOF_CODE();
         break;
      }
   }

   return TRUE;
}

/*************************************************************
* dsPARAM_STORE::WriteStreamDict()
*************************************************************/
BOOL dsPARAM_STORE::WriteStreamDict(fioFILE_MEM *stream)
{

   // Check version
   int version_1 = PARAM_VERSION_DEF_INI;
   int idxVersion = Find(psSTRID(versionTag, FALSE));
   if (idxVersion != -1) {
      GetVal(idxVersion, version_1);
   }
   stream->WritePacked(version_1);

   int itemId;
   for (int i = 0; i < items.Length(); ++i) {
      itemId = dictionary_indexes.Contains(items[i].GetId(), &itemId) ? itemId : PARAM_STREAM_NOT_IN_DICT;
      stream->WritePacked(itemId);
      if (itemId == PARAM_STREAM_NOT_IN_DICT) {
         stream->Write(dsSTRING(items[i].GetId().CStr()));
         stream->WritePacked(items[i].data.GetTypeId());
#ifndef _RETAIL
         dictionary_new.InsertSorted(items[i].GetId(), dsCMP(), dsVECTOR<psSTRID>::INS_DUP_IGNORE);
#endif //_RETAIL
      }
      switch(items[i].data.GetTypeId()) {
      case TYPE_ID(bool):
         {
            BOOL bTemp;
            bool bSrc;
            items[i].GetVal<bool>(bSrc);
            bTemp = bSrc;
            stream->WritePacked(bTemp);
         }
         break;
      case TYPE_ID(int):
         {
            int iTemp;
            items[i].GetVal<int>(iTemp);
            stream->WritePacked(iTemp);
         }
         break;
      case TYPE_ID(DWORD):
         {
            DWORD dTemp;
            items[i].GetVal<DWORD>(dTemp);
            stream->WritePacked(dTemp);
         }
         break;
      case TYPE_ID(float):
         {
            float fTemp;
            items[i].GetVal<float>(fTemp);
            stream->Write(fTemp);
         }
         break;
      case TYPE_ID(dsSTRING):
         {
            dsSTRING sTemp;
            items[i].GetVal<dsSTRING>(sTemp);
            stream->Write(sTemp);
         }
         break;
      }
   }
   stream->WritePacked(PARAM_STREAM_END);

#ifndef _RETAIL
   for (int i = itemsRead; i < items.Length(); ++i) {
      itemId = dictionary_indexes.Contains(items[i].GetId(), &itemId) ? itemId : PARAM_STREAM_NOT_IN_DICT;
      if (itemId != PARAM_STREAM_NOT_IN_DICT) {
         apForceLog("config.log", "Found in dictionary, but skip in user.ini: '%s'\n", items[i].GetId().CStr());
      }
   }
#endif
   return TRUE;
}

/*************************************************************
* dsPARAM_STORE::Find()
*************************************************************/
int dsPARAM_STORE::Find(psSTRID id)
{
   int idx = -1;
   return indexes.Contains(id, &idx) ? idx : -1;
}

/*************************************************************
* dsPARAM_STORE::Find()
*************************************************************/
int dsPARAM_STORE::GetType(int idx)
{
   if (idx < 0 || idx >= items.Length()) {
      ASSERT(FALSE);
      return NULL;
   }
   return items[idx].data.GetTypeId();
}

/*************************************************************
* dsPARAM_STORE::Clear()
*************************************************************/
void dsPARAM_STORE::Clear(void)
{
   items.Clear();
   indexes.Clear();
   return;
}
